using System;

namespace PWEmu.ZLib
{
    /// <summary>
    /// ZLib stream class.
    /// Doesn't inherit System.IO.Stream!
    /// </summary>
    sealed public class ZStream
    {
        private const int _MAX_WBITS = 15; // 32K LZ77 window		
        private static readonly int _DEF_WBITS = _MAX_WBITS;

        private const int _MAX_MEM_LEVEL = 9;

        /// <summary>Next input byte.</summary>
        public byte[] nextIn;
        public int nextInIndex;
        /// <summary>Number of bytes available at nextIn.</summary>
        public int availableIn;
        /// <summary>Total nb of input bytes read so far.</summary>
        public long totalIn;

        /// <summary>Next output byte should be put there.</summary>
        public byte[] nextOut;
        public int nextOutIndex;
        /// <summary>Remaining free space at nextout.</summary>
        public int availableOut;
        /// <summary>Total nb of bytes output so far.</summary>
        public long totalOut;

        public string message;

        internal Deflate _dstate;
        internal Inflate _istate;

        /// <summary>Best guess about the data type: ascii or binary.</summary>
        internal int _dataType; // 

        public long adler;

        internal Adler32 _adler = new Adler32();

        public int InflateInit()
        {
            return InflateInit(_DEF_WBITS);
        }

        public int InflateInit(int w)
        {
            _istate = new Inflate();
            return _istate.InflateInit(this, w);
        }

        public int Inflate(int f)
        {
            if (_istate == null)
                return ZLibConst.Status.Z_STREAM_ERROR;
            return _istate.Inflater(this, f);
        }

        public int InflateEnd()
        {
            if (_istate == null)
                return ZLibConst.Status.Z_STREAM_ERROR;
            int ret = _istate.InflateEnd(this);
            _istate = null;
            return ret;
        }

        public int InflateSync()
        {
            if (_istate == null)
                return ZLibConst.Status.Z_STREAM_ERROR;
            return _istate.InflateSync(this);
        }

        public int InflateSetDictionary(byte[] dictionary, int dictLength)
        {
            if (_istate == null)
                return ZLibConst.Status.Z_STREAM_ERROR;
            return _istate.InflateSetDictionary(this, dictionary, dictLength);
        }

        public int DeflateInit(int level)
        {
            return DeflateInit(level, _MAX_WBITS);
        }

        public int DeflateInit(int level, int bits)
        {
            _dstate = new Deflate();
            return _dstate.deflateInit(this, level, bits);
        }

        public int Deflate(int flush)
        {
            if (_dstate == null)
            {
                return ZLibConst.Status.Z_STREAM_ERROR;
            }
            return _dstate.deflate(this, flush);
        }

        public int DeflateEnd()
        {
            if (_dstate == null)
                return ZLibConst.Status.Z_STREAM_ERROR;
            int ret = _dstate.deflateEnd();
            _dstate = null;
            return ret;
        }

        public int DeflateParams(int level, int strategy)
        {
            if (_dstate == null)
                return ZLibConst.Status.Z_STREAM_ERROR;
            return _dstate.deflateParams(this, level, strategy);
        }

        public int DeflateSetDictionary(byte[] dictionary, int dictLength)
        {
            if (_dstate == null)
                return ZLibConst.Status.Z_STREAM_ERROR;
            return _dstate.deflateSetDictionary(this, dictionary, dictLength);
        }


        /// <summary>
        /// Flush as much pending output as possible. All deflate() output goes
        /// through this function so some applications may wish to modify it
        /// to avoid allocating a large strm->next_out buffer and copying into it.
        /// </summary>
        /// <seealso cref="Read"/>
        internal void FlushPending()
        {
            int len = _dstate.pending;

            if (len > availableOut)
                len = availableOut;
            if (len == 0)
                return;

            if (_dstate.pending_buf.Length <= _dstate.pending_out || nextOut.Length <= nextOutIndex || _dstate.pending_buf.Length < (_dstate.pending_out + len) || nextOut.Length < (nextOutIndex + len))
            {
                //System.Console.Out.WriteLine(dstate.pending_buf.Length + ", " + dstate.pending_out + ", " + next_out.Length + ", " + next_out_index + ", " + len);
                //System.Console.Out.WriteLine("avail_out=" + avail_out);
            }

            Array.Copy(_dstate.pending_buf, _dstate.pending_out, nextOut, nextOutIndex, len);

            nextOutIndex += len;
            _dstate.pending_out += len;
            totalOut += len;
            availableOut -= len;
            _dstate.pending -= len;
            if (_dstate.pending == 0)
            {
                _dstate.pending_out = 0;
            }
        }

        /// <summary>
        /// Read a new buffer from the current input stream, update the adler32
        /// and total number of bytes read. All deflate() input goes through
        /// this function so some applications may wish to modify it to avoid
        /// allocating a large strm->next_in buffer and copying from it.
        /// </summary>
        /// <param name="buf"></param>
        /// <param name="start"></param>
        /// <param name="size"></param>
        /// <returns></returns>
        /// <seealso cref="FlushPending"/>
        internal int Read(byte[] buf, int start, int size)
        {
            int len = availableIn;

            if (len > size)
                len = size;
            if (len == 0)
                return 0;

            availableIn -= len;

            if (_dstate.noheader == 0)
            {
                adler = _adler.Calculate(adler, nextIn, nextInIndex, len);
            }
            Array.Copy(nextIn, nextInIndex, buf, start, len);
            nextInIndex += len;
            totalIn += len;
            return len;
        }

        /// <summary>
        /// Clears the buffer.
        /// </summary>
        public void Free()
        {
            nextIn = null;
            nextOut = null;
            message = null;
            _adler = null;
        }
    }
}